package top.zhangjianyong.blog.config.filter;

import com.alibaba.fastjson2.JSONObject;
import jakarta.servlet.*;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import top.zhangjianyong.blog.config.AntiHotlinkConfig;
import top.zhangjianyong.blog.response.ApiResponse;
import top.zhangjianyong.blog.service.AntiHotlinkStatsService;
import top.zhangjianyong.blog.utils.RefererUtil;
import top.zhangjianyong.blog.utils.SignatureUtil;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Pattern;

/**
 * 防盗链过滤器
 * 统一处理文件访问的防盗链验证
 *
 * @author zhangjianyong
 * @since 2024/12/19
 */
@Slf4j
@Component
public class AntiHotlinkFilter implements Filter {

    @Autowired
    private AntiHotlinkConfig config;

    @Autowired
    private RefererUtil refererUtil;

    @Autowired(required = false)
    private AntiHotlinkStatsService statsService;

    /**
     * 需要防盗链验证的URL模式
     */
    private static final List<Pattern> PROTECTED_PATTERNS = Arrays.asList(
        Pattern.compile("/api/file/\\d+/.*"),
        Pattern.compile("/api/portal/file/\\d+/.*")
    );

    /**
     * 忽略的URL路径（不需要防盗链验证）
     */
    private static final List<String> IGNORE_URI = Arrays.asList(
        "/api/file/upload",
        "/api/file/signed-url",
        "/api/file/signed-portal-url"
    );

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
        throws IOException, ServletException {

        HttpServletRequest request = (HttpServletRequest) servletRequest;
        String uri = request.getRequestURI();

        // 检查是否需要防盗链验证
        if (shouldApplyAntiHotlink(uri)) {
            log.debug("应用防盗链验证: {}", uri);

            if (!validateAccess(request)) {
                String referer = request.getHeader("Referer");
                String userAgent = request.getHeader("User-Agent");
                String ip = getClientIp(request);

                log.warn("防盗链验证失败: {} -> {}", referer, uri);

                // 记录攻击统计
                if (statsService != null) {
                    try {
                        statsService.recordAttack(ip, referer, userAgent, uri, "防盗链验证失败");
                    } catch (Exception e) {
                        log.error("记录防盗链攻击统计失败", e);
                    }
                }

                printResponse(ApiResponse.fail("访问被拒绝，请通过正确的页面访问"), servletResponse);
                return;
            }
        }

        filterChain.doFilter(servletRequest, servletResponse);
    }

    /**
     * 检查是否需要应用防盗链验证
     *
     * @param uri 请求URI
     * @return 是否需要验证
     */
    private boolean shouldApplyAntiHotlink(String uri) {
        // 如果防盗链功能未启用，直接放行
        if (!config.isEnabled()) {
            return false;
        }

        // 检查是否在忽略列表中
        if (IGNORE_URI.stream().anyMatch(uri::startsWith)) {
            return false;
        }

        // 检查是否匹配受保护的URL模式
        return PROTECTED_PATTERNS.stream().anyMatch(pattern -> pattern.matcher(uri).matches());
    }

    /**
     * 验证访问权限
     *
     * @param request HTTP请求
     * @return 是否有权限访问
     */
    private boolean validateAccess(HttpServletRequest request) {
        String uri = request.getRequestURI();

        // 1. 检查是否为内部请求（API调用）
        if (refererUtil.isInternalRequest(request)) {
            log.debug("内部请求，允许访问: {}", uri);
            return true;
        }

        // 2. 检查Referer是否合法
        if (config.isEnableReferer() && !refererUtil.isValidReferer(request)) {
            log.warn("Referer验证失败: {}", uri);
            return false;
        }

        // 3. 验证签名（如果启用了签名验证）
        if (config.isEnableSignature()) {
            String timestamp = request.getParameter("t");
            String signature = request.getParameter("s");

            if (timestamp != null && signature != null) {
                // 从URI中提取文件名和类型
                String[] pathParts = uri.split("/");
                if (pathParts.length >= 4) {
                    try {
                        Integer type = Integer.parseInt(pathParts[pathParts.length - 2]);
                        String fileName = pathParts[pathParts.length - 1];
                        Long timestampLong = Long.parseLong(timestamp);

                        if (SignatureUtil.verifySignature(fileName, type, timestampLong, signature)) {
                            log.debug("签名验证成功: {}", uri);
                            return true;
                        } else {
                            log.warn("签名验证失败: {}", uri);
                            return false;
                        }
                    } catch (NumberFormatException e) {
                        log.warn("参数解析失败: {}", uri, e);
                        return false;
                    }
                }
            } else {
                // 如果没有签名参数，只验证Referer
                log.debug("无签名参数，仅验证Referer: {}", uri);
                return true;
            }
        }

        return true;
    }

    /**
     * 获取客户端真实IP地址
     *
     * @param request HTTP请求
     * @return 客户端IP地址
     */
    private String getClientIp(HttpServletRequest request) {
        String ip = request.getHeader("X-Forwarded-For");
        if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("X-Real-IP");
        }
        if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("Proxy-Client-IP");
        }
        if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("WL-Proxy-Client-IP");
        }
        if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_CLIENT_IP");
        }
        if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_X_FORWARDED_FOR");
        }
        if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
        }

        // 处理多个IP的情况，取第一个
        if (ip != null && ip.contains(",")) {
            ip = ip.split(",")[0].trim();
        }

        return ip;
    }

    /**
     * 输出响应
     *
     * @param apiResponse API响应
     * @param response    HTTP响应
     * @throws IOException IO异常
     */
    private void printResponse(ApiResponse apiResponse, ServletResponse response) throws IOException {
        response.setContentType("application/json;charset=UTF-8");
        response.getWriter().print(JSONObject.toJSONString(apiResponse));
    }
} 